﻿using Devil;
using GameToolkit.UI;

using GameToolkit;

#if UNITY_EDITOR
#endif
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

namespace GameLogic.UI
{
    //[RequireComponent(typeof(Selectable))]
    public class UISkin : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, ISelectHandler,
        IDeselectHandler, IPointerEnterHandler, IPointerExitHandler, IDataSourceHandler
    {
        struct CachedSkin
        {
            public int id;
            public UISkinAsset.Composite composite;
            public CachedSkin(int id, UISkinAsset.Composite com)
            {
                this.id = id;
                this.composite = com;
            }
        }

        static uint sCacheRef;
        static MinHeap<CachedSkin> sCachedSkins = new MinHeap<CachedSkin>(32, (a) => a.id);

#pragma warning disable CS0649
        public EFeatureSupport m_WarningState = EFeatureSupport.NotExecutable;

        [SerializeField]
        float m_FadeDuration = 0.2f;

        [SerializeField]
        NameID m_CompositeName;

        [SerializeField]
        bool m_AutoSelectOnHighlight;

        [SerializeField, TextArea(5, 8)]
        string m_Config;

        [SerializeField]
        int m_InternalConfigID;

        //[SerializeField, Mask(typeof(EUICompoiteState))]
        EUICompoiteState m_CompositeState;

        IToggle mToggle;
        IAccessable mFeatured;
        Selectable mSelectable;
        UISkinAsset.SkinController mSkinCtrl;
        SelectableNavigation mNav;
        bool mUseCache;
#pragma warning restore
        private void Awake()
        {
            //mToggle = GetComponent<IToggle>();
            mFeatured = GetComponent<IAccessable>();
            mSelectable = GetComponent<Selectable>();
            mToggle = mSelectable as IToggle;
            mNav = GetComponentInParent<SelectableNavigation>();
#if !UNITY_EDITORs
            InitUISkin();
#endif
        }

        private void OnToggleValidate(IToggle tog)
        {
            ValidateState(m_FadeDuration);
        }

        private void OnInteractable(bool interactable)
        {
            ValidateState(m_FadeDuration);
        }

        void InitUISkin()
        {
            mSkinCtrl = null;
            if (m_CompositeName.id != 0)
                mSkinCtrl = UISkinAsset.SharedInstance.CreateSkinController(transform, m_CompositeName.id);
            if (mSkinCtrl == null && m_InternalConfigID != 0)
            {
                var cache = sCachedSkins.FindIndexIntoChildren(m_InternalConfigID);
                if (cache != -1 && sCachedSkins[cache].composite != null)
                {
                    mSkinCtrl = new UISkinAsset.SkinController(sCachedSkins[cache].composite, transform);
                }
                else
                {
                    var com = UISkinAsset.SharedInstance.CreateSkinComposite(name, m_Config, EDataType.Json);
                    if (com != null)
                    {
                        sCachedSkins.Add(new CachedSkin(m_InternalConfigID, com));
                        mSkinCtrl = new UISkinAsset.SkinController(com, transform);
                        mUseCache = true;
                    }
                }
            }
        }

        private void OnEnable()
        {
            if (mToggle != null)
                mToggle.IsOnChanged += OnToggleValidate;
#if MY_UGUI
            mSelectable.OnInteractableChanged += OnInteractable;
#endif
            if (EventSystem.current != null && EventSystem.current.currentSelectedGameObject == gameObject)
                m_CompositeState |= EUICompoiteState.Selected;
#if UNITY_EDITORs
            InitUISkin();
#endif
            ValidateState(0f);
            if (mUseCache)
                sCacheRef++;
        }

        private void OnDisable()
        {
            if (mUseCache && sCacheRef > 0)
            {
                sCacheRef--;
                if (sCacheRef == 0)
                {
                    sCachedSkins.Cleanup();
                }
            }
#if MY_UGUI
            mSelectable.OnInteractableChanged -= OnInteractable;
#endif
            if (mSkinCtrl != null)
                mSkinCtrl.Release();
            m_CompositeState &= ~(EUICompoiteState.Pressed | EUICompoiteState.Highlight | EUICompoiteState.Selected);
            if (mToggle != null)
                mToggle.IsOnChanged -= OnToggleValidate;

        }

        public void OnRefreshData()
        {
            //if (mFeatured != null)
            ValidateState(m_FadeDuration);
        }

        private void ValidateState(float duration)
        {
            if (mSkinCtrl != null)
            {
                var stat = m_CompositeState;
                if (mToggle != null)
                {
                    if (mToggle.IsOn)
                    {
                        stat |= EUICompoiteState.Selected;
                    }
                    else
                    {
                        stat &= ~EUICompoiteState.Selected;
                    }
                }
                var feature = mFeatured == null ? EFeatureSupport.Support : mFeatured.GetExecutionAccess().access;
                if (!mSelectable.interactable || feature != EFeatureSupport.Support)
                    stat |= EUICompoiteState.Disabled;
                if (feature == m_WarningState)
                    stat |= EUICompoiteState.Warning;
#if UNITY_EDITOR
                stateName = ParallelUtils.ToMask((int)stat, ",", stats);
#endif
                mSkinCtrl.Activite(stat, duration);
            }
        }

        public void OnSelect(BaseEventData eventData)
        {
            m_CompositeState |= EUICompoiteState.Selected;
            ValidateState(m_FadeDuration);
        }

        public void OnDeselect(BaseEventData eventData)
        {
            m_CompositeState &= ~EUICompoiteState.Selected;
            ValidateState(m_FadeDuration);
        }

        public void OnPointerDown(PointerEventData eventData)
        {
            m_CompositeState |= EUICompoiteState.Pressed;
            ValidateState(m_FadeDuration);
            if (mNav != null)
            {
                mNav.SetFinalSelected(gameObject);
            }
        }

        public void OnPointerUp(PointerEventData eventData)
        {
            m_CompositeState &= ~EUICompoiteState.Pressed;
            ValidateState(m_FadeDuration);
        }

        public void OnPointerEnter(PointerEventData eventData)
        {
            m_CompositeState |= EUICompoiteState.Highlight;
            if (m_AutoSelectOnHighlight && (m_CompositeState & EUICompoiteState.Selected) == 0)
            {
                EventSystem.current.SetSelectedGameObject(gameObject);
            }
            ValidateState(m_FadeDuration);
        }

        public void OnPointerExit(PointerEventData eventData)
        {
            m_CompositeState &= ~EUICompoiteState.Highlight;
            ValidateState(m_FadeDuration);
        }

#if UNITY_EDITOR
        string stateName;
        string[] stats = { "Selected", "Highlight", "Disabled", "Pressed" };
        //    Normal = 0,
        //Selected = 1,
        //Highlight = 2,
        //Disabled = 4,
        //Pressed = 8,};

        int CalculateID(string config)
        {
            var id = 0;
            var len = config == null ? 0 : config.Length;
            char qut = '\0';
            for (int i = 0; i < len; i++)
            {
                var c = config[i];
                if (qut == '"' || qut == '\'')
                {
                    id = MathExt.Hash(id, c);
                    if (c == qut)
                        qut = '\0';
                }
                else if (c != ' ' && c != '\t' && c != '\r' && c != '\n')
                {
                    id = MathExt.Hash(id, c);
                    if (c == '"' || c == '\'')
                        qut = c;
                }
            }
            return id;
        }

        private void OnValidate()
        {
            var id = CalculateID(m_Config);
            if (id != m_InternalConfigID)
            {
                m_InternalConfigID = id;
                UnityEditor.EditorUtility.SetDirty(this);
            }
        }

        private void OnGUI()
        {
            var sel = UnityEditor.Selection.activeGameObject;
            if (sel == gameObject)
                GUILayout.Label(stateName);
        }
#endif
    }
}